package xj.graph2d.graph;

import java.awt.geom.Dimension2D;
import java.awt.geom.Point2D;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Set;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import static xj.graph2d.graph.GraphConstants.*;

public class AbstractGraphNode <EdgeType extends GraphEdge>
  extends AbstractGraphComponent 
  implements GraphNode<EdgeType> { 

  public AbstractGraphNode() {
    super(NODE);
  }

  public AbstractGraphNode(String name) {
    super(NODE, name);
  }

  public AbstractGraphNode(String name, int nodeType) {
    super(NODE, name);
    this.nodeType = nodeType;
  }

  public AbstractGraphNode(Subgraph subg) {
    super(NODE, subg);
  }

  public AbstractGraphNode(Subgraph subg, String name) {
    super(NODE, subg, name);
  }

  public AbstractGraphNode(Subgraph subg, String name, int nodeType) {
    super(NODE, subg, name);
    this.nodeType = nodeType;
  }

  @Override
  public int hashCode() {
    if (name != null) {
      return name.hashCode();
    } else {
      return 0;
    }
  }

  @Override
  public int getType() {
    return NODE;
  }

  @Override
  public boolean isNode() {
    return true;
  }

  public Point2D getULCorner() {
    if (p != null) {
      if (d != null) {
	return new Point2D.Float((float) (p.getX() - d.getWidth() / 2),
	    (float) (p.getY() - d.getHeight() / 2));
      } else {
	return p;
      }
    } else {
      return null;
    }
  }

  public Point2D getLRCorner() {
    if (p != null) {
      if (d != null) {
	return new Point2D.Float((float) (p.getX() + d.getWidth() / 2),
	    (float) (p.getY() + d.getHeight() / 2));
      } else {
	return p;
      }
    } else {
      return null;
    }
  }

  public Point2D getCenter() {
    return p;
  }

  public void setLocation(Point2D p) {
    if (this.p != null && p != null) {
      this.p.setLocation(p);
    } else {
      this.p = p;
    }
  }

  public void setLocation(double x, double y) {
    if (p != null) {
      p.setLocation(x, y);
    } else {
      p = new Point2D.Float((float) x, (float) y);
    }
  }

  public int isInside(Point2D p0) {
    if (p0 != null && p != null && d != null) {
      double px = p.getX();
      double py = p.getY();
      double px0 = p0.getX();
      double py0 = p0.getY();
      double w = d.getWidth();
      double h = d.getHeight();
      if ((px0 >= (px - w / 2)) && (px0 < (px + w / 2))
	  && (py0 >= (py - h / 2)) && (py0 < (py + h / 2))) {
	return PointInside;
      }
    }
    return PointOutside;
  }

  public Dimension2D getSize() {
    return d;
  }

  public void setSize(double width, double height) {
    if (d != null) {
      d.setSize(width, height);
    } else {
      d = new Dimension2DFloat((float) width, (float) height);
    }
  }

  public boolean isMovable() {
    return movable;
  }

  public void setMovable(boolean movable) {
    this.movable = movable;
  }

  public void move(double dx, double dy) {
    if (movable && p != null) {
      p.setLocation(p.getX() + dx, p.getY() + dy);
    }
  }

  public int getNodeType() {
    return nodeType;
  }

  public void setNodeType(int nodeType) {
    this.nodeType = nodeType;
  }

  public int getCount() {
    return count;
  }

  public void setCount(int count) {
    this.count = count;
  }

  public int getHeight() {
    return height;
  }

  public void setHeight(int height) {
    this.height = height;
  }

  public int getRank() {
    return rank;
  }

  public void setRank(int rank) {
    this.rank = rank;
  }

  public Set<Connector> getConnectors() {
    return connectors;
  }

  public void addConnector(Connector c) {
    if (connectors != null && c != null) {
      connectors.add(c);
    }
  }

  public void removeConnector(Connector c) {
    if (connectors != null && c != null) {
      connectors.remove(c);
    }
  }

  public Set<EdgeType> leavingEdges() {
    return leavingEdgeSet;
  }

  public Set<EdgeType> arrivingEdges() {
    return arrivingEdgeSet;
  }

  public int getDegree() {
    return getInDegree() + getOutDegree();
  }

  public int getInDegree() {
    if (arrivingEdgeSet != null) {
      return arrivingEdgeSet.size();
    }
    return 0;
  }

  public int getOutDegree() {
    if (leavingEdgeSet != null) {
      return leavingEdgeSet.size();
    }
    return 0;
  }

  /**
   * Print the node description to the provided stream.
   * 
   * @param out
   *                the output text stream for writing the description.
   */
  public void printNode(PrintWriter out) {
    this.printElement(out);
  }

  /* Added by Naveen Vemuri, May 3, 2003. */
  public String xgmmlLabel = "node";

  public String getXGMMLLabel() {
    return this.xgmmlLabel;
  }

  public Element createXGMMLElement(Element graphElement, Document doc, int id) {
    Element nodeElement = doc.createElement(XGMML_NODE_TAG);
    /* can use attributes id, name, label, and labelanchor. */
    nodeElement.setAttribute("id", new Integer(id).toString());
    nodeElement.setAttribute("name", this.getName());
    nodeElement.setAttribute("label", this.getXGMMLLabel());

    graphElement.appendChild(nodeElement);
    return graphElement;
  }

  /* End of additions. */

  @Override
  protected String getDefaultNamePrefix() {
    if (prefix != null) {
      return prefix + "n";
    } else {
      return "n";
    }
  }

  protected static int _id = 1;

  @Override
  protected int getNextId() {
    if (false) {
      System.out.println("AbstractGraphNode.getId(): " + AbstractGraphNode._id);
      try {
	throw new Exception("AbstractGraphNode.getId()");
      } catch (Exception e) {
	e.printStackTrace();
      }
    }

    return AbstractGraphNode._id++;
  }

  protected int nodeType;

  protected Point2D p;

  protected Dimension2D d;

  protected boolean movable;

  protected int count;

  protected int height;

  protected int rank;

  protected Set<Connector> connectors;

  protected Set<EdgeType> leavingEdgeSet = new HashSet<EdgeType>();

  protected Set<EdgeType> arrivingEdgeSet = new HashSet<EdgeType>();

}
